{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Component\n",
    "\n",
    "A `Component` is like an empty canvas, in which you can add polygons, references to other Components and ports (to connect to other components)\n",
    "\n",
    "![](https://i.imgur.com/oeuKGsc.png)\n",
    "\n",
    "In gdsfactory **all dimensions** are in **microns**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1",
   "metadata": {},
   "source": [
    "## Polygons\n",
    "\n",
    "You can add polygons to different layers. By default all units in gdsfactory are in um."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "# Create a blank component (essentially an empty GDS cell with some special features).\n",
    "c = gf.Component()\n",
    "p1 = c.add_polygon([(-8, -6), (6, 8), (7, 17), (9, 5)], layer=(1, 0))\n",
    "c.write_gds(\"demo.gds\")  # Write it to a GDS file. You can open it in klayout.\n",
    "c.show()  # Show it in klayout.\n",
    "c.plot()  # Plot it in jupyter notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "**Exercise** :\n",
    "\n",
    "Make a component similar to the one above that has a second polygon in layer (2, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "# Create some new geometry from the functions available in the geometry library.\n",
    "t = gf.components.text(\"Hello!\")\n",
    "r = gf.components.rectangle(size=(5, 10), layer=(2, 0))\n",
    "\n",
    "# Add references to the new geometry to c, our blank component.\n",
    "text1 = c.add_ref(t)  # Add the text we created as a reference.\n",
    "# Using the << operator (identical to add_ref()), add the same geometry a second time.\n",
    "text2 = c << t\n",
    "r = c << r  # Add the rectangle we created.\n",
    "\n",
    "# Now that the geometry has been added to \"c\", we can move everything around:\n",
    "text1.movey(25)\n",
    "text2.move((5, 30))\n",
    "text2.rotate(45)\n",
    "r.movex(-15)\n",
    "\n",
    "print(c)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "p1 = c.add_polygon(\n",
    "    [(-8, -6), (6, 8), (7, 17), (9, 5)], layer=(1, 0)\n",
    ")  # DPolygons are in um\n",
    "p2 = c.get_region(layer=(1, 0))  # Get the region of the polygon.\n",
    "p3 = p2.size(2000)  # Regions are in nm!\n",
    "c.add_polygon(p3, layer=(2, 0))  # Add the region to the component.\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "For operating boolean operations KLayout regions are very useful.\n",
    "\n",
    "Notice that many KLayout objects are in database units (DBU) which usually is 1nm, therefore we need to convert a DPolygon (in um) to DBU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "p1 = c.add_polygon(\n",
    "    [(-8, -6), (6, 8), (7, 17), (9, 5)], layer=(1, 0)\n",
    ")  # Polygons are in um.\n",
    "r1 = c.get_region(layer=(1,0))  # Regions are in DBU (1 nm in this case).\n",
    "r2 = r1.sized(2000)  # Regions are in DBU.\n",
    "r3 = r2 - r1\n",
    "\n",
    "c.add_polygon(r3, layer=(2, 0))  # Add the region to the component.\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component() # Creates a new blank component.\n",
    "p1 = [(-8, -6), (6, 8), (7, 17), (9, 5)] # This adds a list of coordinates (p1) and adds it to the component c on the layer (1, 0).\n",
    "s1 = c.add_polygon(p1, layer=(1, 0)) \n",
    "r1 = gf.Region(s1.polygon) # To manipulate the shape, the polygon is converted into a region object (r1).\n",
    "r2 = r1.sized(2000)  # In DBU, 1 DBU = 1 nm, size it by 2000 nm = 2um.\n",
    "r3 = r2 - r1 # We then take the larger region (r2) and subtract the smaller region (r)1 from it. The result is a new region (r3).\n",
    "c.add_polygon(r3, layer=(2, 0)) # The newly created r3 is then added to the component c, but on the layer (2, 0) this time.\n",
    "c.plot() # This command generates a plot of the component, allowing you to see the final result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "p1 = [(-8, -6), (6, 8), (7, 17), (9, 5)]\n",
    "s1 = c.add_polygon(p1, layer=(1, 0))\n",
    "r1 = gf.Region(s1.polygon)\n",
    "r2 = r1.sized(2000)  # In DBU, 1 DBU = 1 nm, size it by 2000 nm = 2um.\n",
    "r3 = r2 - r1\n",
    "\n",
    "c2 = gf.Component() # This portion creates an empty component in which r3 gets added into, this happens on the layer (2, 0) and then gets plotted.\n",
    "c2.add_polygon(r3, layer=(2, 0))\n",
    "c2.plot()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "## Connect **ports**\n",
    "\n",
    "Components can have a \"Port\" that allows you to connect ComponentReferences together like legos.\n",
    "\n",
    "You can write a simple function to make a rectangular straight, assign ports to the ends, and then connect those rectangles together.\n",
    "\n",
    "Notice that `connect` transforms each reference but things will not remain connected if you move any of the references afterwards.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The straight function, which is a parametric component generator, gets defined here.\n",
    "# It is a reusable recipe that can create a straight waveguide of any specified length, width, and layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "def straight(length=10, width: float = 1, layer=(1, 0)): \n",
    "    c = gf.Component()\n",
    "    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer) # This draws the main rectangular body of the waveguide.\n",
    "    c.add_port( # This adds two connection points, or ports, which are essential for connecting this component to others.\n",
    "        name=\"o1\", center=(0, width / 2), width=width, orientation=180, layer=layer # \"o1\" is the input port and is facing left due to rotation (orientation=180)\n",
    "    )\n",
    "    c.add_port(\n",
    "        name=\"o2\", center=(length, width / 2), width=width, orientation=0, layer=layer # \"o2\" is the output port and is facing right.\n",
    "    )\n",
    "    return c\n",
    "\n",
    "\n",
    "c = gf.Component()\n",
    "\n",
    "# Three waveguides are created. They have different lengths and sit on different layers, but all have the same width.\n",
    "wg1 = c << straight(length=6, width=2.5, layer=(1, 0)) \n",
    "wg2 = c << straight(length=6, width=2.5, layer=(2, 0))\n",
    "wg3 = c << straight(length=15, width=2.5, layer=(3, 0))\n",
    "wg2.movey(10) # This moves wg2 up by 10 µm.\n",
    "wg2.rotate(10) # this rotates wg2 by 10 degrees.\n",
    "\n",
    "wg3.movey(20) # This moves wg3 up by 20 µm.\n",
    "wg3.rotate(15) # This rotates wg3 by 15 degrees.\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13",
   "metadata": {},
   "source": [
    "Now we can connect everything together using the ports:\n",
    "\n",
    "Each straight has two ports: 'o1' and 'o2', respectively on the East and West sides of the rectangular straight component. These are arbitrary\n",
    "names defined in our straight() function above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let us keep wg1 in place on the bottom, and connect the other straights to it.\n",
    "# To do that, on wg2 we will take the \"o1\" port and connect it to the \"o2\" on wg1:\n",
    "wg2.connect(\"o1\", wg1.ports[\"o2\"], allow_layer_mismatch=True)\n",
    "\n",
    "# Next, on wg3 let us take the \"o1\" port and connect it to the \"o2\" on wg2:\n",
    "wg3.connect(\"o1\", wg2.ports[\"o2\"], allow_layer_mismatch=True)\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "Ports can be added by copying existing ports. In the example below, ports are added at the component-level on c from the existing ports of children wg1 and wg3\n",
    "(i.e. eastmost and westmost ports)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.add_port(\"o1\", port=wg1.ports[\"o1\"]) \n",
    "c.add_port(\"o2\", port=wg3.ports[\"o2\"])\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "You can show the ports by adding port pins with triangular shape or using the show_ports plugin\n",
    "\n",
    "![](https://i.imgur.com/Y3CuM30.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.draw_ports()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19",
   "metadata": {},
   "source": [
    "Also you can visualize ports in klayout as the ports are stored in the GDS cell metadata."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "## Move and rotate Instances\n",
    "\n",
    "You can move, rotate, and reflect component instances.\n",
    "\n",
    "There are two main types of movement:\n",
    "\n",
    "1. Using Integer DataBaseUnits (DBU) (default), in most foundries, 1 DBU = 1nm\n",
    "2. Using Decimals Floats. Where 1.0 represents 1.0um"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "wg1 = c << straight(length=1, layer=(1, 0))\n",
    "wg2 = c << straight(length=2, layer=(2, 0))\n",
    "wg3 = c << straight(length=3, layer=(3, 0))\n",
    "\n",
    "# Shift the second straight we created over by dx = 2, dy = 2 um. D stands for decimal.\n",
    "wg2.move((2.0, 2.0))\n",
    "\n",
    "# Then, move again the third straight by 3um.\n",
    "wg3.movex(3)  # Equivalent to wg3.move(3).\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "You can also define the positions relative to other references."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "\n",
    "wg1 = c << straight(length=1, layer=(1, 0))\n",
    "wg2 = c << straight(length=2, layer=(2, 0))\n",
    "wg3 = c << straight(length=3, layer=(3, 0))\n",
    "\n",
    "# Shift the second straight we created over so that the xmin matches wg1.xmax.\n",
    "wg2.xmin = wg1.xmax\n",
    "\n",
    "# Then, leave a 1um gap with on the last straight.\n",
    "wg3.xmin = wg2.xmax + 1\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24",
   "metadata": {},
   "source": [
    "## Ports\n",
    "\n",
    "Your straights wg1/wg2/wg3 are references to other waveguide components.\n",
    "\n",
    "If you want to add ports to the new Component `c` you can use `add_port`, this allows you to create a new port or use a reference of an existing port from the underlying reference."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25",
   "metadata": {},
   "source": [
    "You can access the ports of a Component or ComponentReference"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "\n",
    "def straight(length: float = 10, width: float = 1, layer=(1, 0)):\n",
    "    c = gf.Component()\n",
    "    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)\n",
    "    c.add_port(\n",
    "        name=\"o1\", center=(0, width / 2), width=width, orientation=180, layer=layer\n",
    "    )\n",
    "    c.add_port(\n",
    "        name=\"o2\", center=(length, width / 2), width=width, orientation=0, layer=layer\n",
    "    )\n",
    "    return c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "Now that your component `c` is a multi-straight component, you can add references to that component in a new blank Component `c2`, then add two references and shift one to see the movement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "c2 = gf.Component()\n",
    "wg1 = straight(length=10)\n",
    "wg2 = straight(length=10, layer=(2, 0))\n",
    "mwg1_ref = c2.add_ref(wg1)\n",
    "mwg2_ref = c2.add_ref(wg2)\n",
    "mwg2_ref.movex(10) # This moves mwg2_ref to the right by 10µm.\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Like before, let us connect mwg1 and mwg2 together.\n",
    "mwg1_ref.connect(port=\"o2\", other=mwg2_ref.ports[\"o1\"], allow_layer_mismatch=True)\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30",
   "metadata": {},
   "source": [
    "## Labels\n",
    "\n",
    "You can add abstract GDS labels to annotate your Components, in order to record information\n",
    "directly into the final GDS file without putting any extra geometry onto any layer.\n",
    "This label will display in a GDS viewer, but will not be rendered or printed\n",
    "like the polygons created by `gf.components.text()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This adds the text \"First label\" to the component. The position is set to mwg1_ref.dcenter,\n",
    "# which automatically places the label at the center of the first waveguide instance (mwg1_ref).\n",
    "c2.add_label(text=\"First label\", position=mwg1_ref.dcenter)\n",
    "# Same as the above, except the text is \"Second label\" and it is referring to the instance (mwg2_ref).\n",
    "c2.add_label(text=\"Second label\", position=mwg2_ref.dcenter)\n",
    "\n",
    "# Labels are useful for recording information.\n",
    "c2.add_label(\n",
    "    # An f-string (text) is used to create a dynamic and multi-line (\\n) label. \n",
    "    # It automatically calculates the total width of the component (c2.xsize) and embeds it in the text.\n",
    "    text=f\"The x size of this\\nlayout is {c2.xsize}\",\n",
    "    position=(c2.x, c2.y), # The label is placed at the component's origin (c2.x, c2.y), which is typically (0, 0).\n",
    "    layer=(10, 0), # The label is placed on layer 10.\n",
    ")\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32",
   "metadata": {},
   "source": [
    "Another simple example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "r = c << gf.components.rectangle(size=(1, 1))\n",
    "r.x = 0\n",
    "r.y = 0\n",
    "c.add_label(\n",
    "    text=\"Demo label\",\n",
    "    position=(0, 0),\n",
    "    layer=(1, 0),\n",
    ")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34",
   "metadata": {},
   "source": [
    "## Boolean shapes\n",
    "\n",
    "If you want to subtract one shape from another, merge two shapes, or\n",
    "perform an XOR on them, you can do that with the `boolean()` function.\n",
    "\n",
    "\n",
    "The ``operation`` argument should be {not, and, or, xor, 'A-B', 'B-A', 'A+B'}.\n",
    "Note that 'A+B' is equivalent to 'or', 'A-B' is equivalent to 'not', and\n",
    "'B-A' is equivalent to 'not' with the operands switched"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "e1 = c.add_ref(gf.components.ellipse(layer=(2, 0)))\n",
    "e2 = c.add_ref(gf.components.ellipse(radii=(10, 6), layer=(2, 0))).movex(2)\n",
    "e3 = c.add_ref(gf.components.ellipse(radii=(10, 4), layer=(2, 0))).movex(5)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A=e2: This sets the first shape for the operation (the one being subtracted from).\n",
    "# B=e1: This sets the second shape (the one that will be removed).\n",
    "# operation=\"not\": In gdsfactory, the \"not\" operation is equivalent to a subtraction (A - B). It keeps the parts of shape A that do not overlap with shape B.\n",
    "c2 = gf.boolean(A=e2, B=e1, operation=\"not\", layer=(2, 0))\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37",
   "metadata": {},
   "source": [
    "## Move Reference by port"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "# This line adds a reference (an instance) of a standard mmi1x2 component to c. \n",
    "# An MMI is a common device used in photonics to split one input signal into two output signals.\n",
    "mmi = c.add_ref(gf.components.mmi1x2())\n",
    "# This adds a reference to a bend_circular component, which is a simple 90-degree curved waveguide.\n",
    "bend = c.add_ref(gf.components.bend_circular(layer=(1, 0)))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39",
   "metadata": {},
   "outputs": [],
   "source": [
    "bend.connect(\"o1\", mmi.ports[\"o2\"])  # Connects follow Source -> Destination syntax.\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40",
   "metadata": {},
   "source": [
    "## Mirror reference\n",
    "\n",
    "By default the mirror works along the y-axis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component()\n",
    "mmi = c.add_ref(gf.components.mmi1x2())\n",
    "bend = c.add_ref(gf.components.bend_circular())\n",
    "bend.connect(\n",
    "    \"o1\", mmi.ports[\"o2\"], mirror=True\n",
    ")  # Connects follow Source -> Destination syntax.\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42",
   "metadata": {},
   "source": [
    "## Write\n",
    "\n",
    "You can write your Component to:\n",
    "\n",
    "- GDS file (Graphical Database System) or OASIS for chips.\n",
    "- gerber for PCB.\n",
    "- STL for 3d printing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "c = gf.components.cross()\n",
    "c.write_gds(\"demo.gds\")\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44",
   "metadata": {},
   "source": [
    "You can see the GDS file in Klayout viewer.\n",
    "\n",
    "Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations ...) in YAML."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.write_gds(\"demo.gds\", with_metadata=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46",
   "metadata": {},
   "source": [
    "⚠️ **Warning!**  \n",
    "\n",
    "If you plan to use **Calibre DRC** or **Cadence** in your design workflow, make sure to set:  \n",
    "\n",
    "```python\n",
    "with_metadata=False\n",
    "```\n",
    "\n",
    "GDSFactory stores ports and settings information inside the GDS file. However, this format may not be compatible with certain EDA tools like Calibre and Cadence. To ensure smooth integration, disable metadata when exporting your GDS files."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47",
   "metadata": {},
   "source": [
    "OASIS is a newer format that can store CAD files and that reduces the size."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.write(\"demo.oas\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49",
   "metadata": {},
   "source": [
    "You can also save it as STL for 3D printing or for device level simulations. For that you need to extrude the polygons using the information in the LayerStack."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {},
   "outputs": [],
   "source": [
    "gf.export.to_stl(c, \"demo.stl\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "51",
   "metadata": {},
   "outputs": [],
   "source": [
    "scene = c.to_3d()\n",
    "scene.show()"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
